package com.distribution.system.openapi.util;

import com.distribution.common.core.redis.RedisCache;
import com.distribution.common.utils.StringUtils;
import com.distribution.system.openapi.domain.Constant;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.UnsupportedEncodingException;
import java.util.*;
import java.util.concurrent.TimeUnit;

import static com.distribution.system.utils.DictionarySortUtil.dictSort;

/**
 * @author: GodLu
 * @create: 2024-04-15 10:03
 * @description: 接口校验工具类
 *  生成有序map,签名，验签
 *  通过appId、timestamp、appSecret做签名
 */
@Slf4j
@Component
public class SignUtils {
    @Autowired
    private RedisCache redisCache;

    /**
     * 所有的参数与应用密钥appSecret 进行排序加密后生成签名
     *
     * @param paramsMap 请求参数
     * @param appSecret 应用id对应的应用密钥
     * @return 生成接口签名
     */
    public static String getVerifySign(Map<String, Object> paramsMap, String appSecret) {
        //先要去掉 前端求参数传过来的 里的 signature
        paramsMap.remove(Constant.SIGN);
        //System.out.println("签名前信息：" + paramsMap);
        // 将key按照字典序排序
        List<String> sortedList = dictSort(paramsMap.keySet().toArray(new String[0]));
        //进行key,value拼接
        // e.g "key1=value1&key2=value2"
        StringBuilder plainText = new StringBuilder();
        for (String key : sortedList) {
            plainText.append(key).append("=").append(paramsMap.get(key)).append("&");
        }
        //拼接应用密钥 appSecret
        plainText.append(appSecret);
        //  System.out.println("plainText = " + plainText);
        //摘要
        String digest = plainText.toString();
        //将digest 转换成UTF-8 的 byte[] 后 使用MD5算法加密，最后将生成的md5字符串转换成大写
        try {
            return DigestUtils.md5Hex(StringUtils.getBytes(digest, "UTF-8")).toUpperCase();
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 上面的流程中，会有个额外的安全处理，防止盗链，我们可以让链接有失效时间
     * 而利用nonce参数，可以防止重复提交,在签名验证成功后，判断是否重复提交,原理就是结合redis，判断是否已经提交过
     *
     * @param appId     应用id
     * @param timeStamp 13位时间戳
     * @param nonce     临时流水号/随机串 ,至少为10位 ，有效期内防重复提交
     * @param signature 接口签名
     * @return 是否重复请求
     */
    public boolean isReplayAttack(String appId, String timeStamp, String nonce, String signature) {
        StringBuilder redisKey = new StringBuilder();
        redisKey.append("IS_REPLAY_ATTACK").append(":")
                .append(Constant.APP_ID).append(":").append(appId)
                .append(Constant.TIME_STAMP).append(":").append(timeStamp)
                .append(Constant.NONCE).append(":").append(nonce)
                .append(Constant.SIGN).append(":").append(signature);

        Object value = redisCache.getCacheObject(redisKey.toString());
        //缓存不为空且与签名一致则为重复提交
        if (value != null && StringUtils.equals(signature, value.toString()))
            return false;
        else
            redisCache.setCacheObject(redisKey.toString(), signature, 50, TimeUnit.SECONDS);
        return false;
    }
}


